#include "tcp_server.h"
#include "config.h"

namespace qtch{
static Logger::ptr logger = QTCH_LOG_NAME("system");

static qtch::ConfigVar<uint64_t>::ptr g_tcp_server_read_timeout = 
    Config::LookUp("tcp_server.read_timeout", (uint64_t)(60 *  1000 * 2), "tcp server read timeout");

TcpServer::TcpServer(qtch::IOManager* worker
             ,qtch::IOManager* ioWorker
             ,qtch::IOManager* acceptWorker)
    :m_worker(worker)
    ,m_acceptWorker(acceptWorker)
    ,m_ioWorker(ioWorker)
    ,m_recvTimeout(g_tcp_server_read_timeout->getValue())
    ,m_name("qtch/1.0.0")
    ,m_isStop(true){
}

TcpServer::~TcpServer(){
    for(auto i : m_socks){
        i->close();
    }
    m_socks.clear();
}

bool TcpServer::bind(qtch::Address::ptr addr, bool ssl){
    std::vector<Address::ptr> addrs;
    std::vector<Address::ptr> failes;
    addrs.push_back(addr);
    return bind(addrs,failes,ssl);
}

bool TcpServer::bind(const std::vector<Address::ptr>& addrs
                    ,std::vector<Address::ptr>& fails
                    ,bool ssl){
    m_ssl = ssl;
    for(auto& addr : addrs){
        Socket::ptr sock = m_ssl ? SSLSocket::CreateTCP(addr) : Socket::CreateTCP(addr);
        if(!sock->bind(addr)){
            QTCH_LOG_ERROR(logger) << "bind fail errno=" << errno
                                   << " errstr=" << strerror(errno)
                                   << " addr=[" << addr << "]";
            fails.push_back(addr);
            continue;
        }
        if(!sock->listen()){
            QTCH_LOG_ERROR(logger) << "listen fail errno=" << errno
                                   << " errstr=" << strerror(errno)
                                   << " addr=[" << addr << "]";
            fails.push_back(addr);
            continue;
        }
        m_socks.push_back(sock);
    }

    if(!fails.empty()) {
        m_socks.clear();
        return false;
    }

    for(auto& i : m_socks){
        QTCH_LOG_INFO(logger) << "type=" << m_type
            << " name=" << m_name
            << " ssl=" << m_ssl
            << " server bind success: "<< i;
    }
    return true;

}

void TcpServer::handleClient(Socket::ptr client){
    QTCH_LOG_INFO(logger) << "handleClient:" << client;
}

void TcpServer::startAccpet(Socket::ptr sock){
    while(!m_isStop){
        Socket::ptr client = sock->accept();
        if(client){
            QTCH_LOG_DEBUG(logger) << "accept client=" << client;
            client -> setRecvTimeout(m_recvTimeout);
            m_ioWorker->schedule(std::bind(&TcpServer::handleClient,shared_from_this(),client));
        }
        else {
            QTCH_LOG_ERROR(logger) <<  "accept errno=" << errno
                << " errstr=" << strerror(errno);
        }
    }
}


bool TcpServer::start(){
    if(!m_isStop){
        return true;
    }
    m_isStop = false;
    for(auto& sock : m_socks){
        m_acceptWorker->schedule(std::bind(&TcpServer::startAccpet, shared_from_this(), sock));
    }
    return true;
}

void TcpServer::stop(){
    m_isStop = true;
    auto self = shared_from_this();
    m_acceptWorker->schedule([this, self](){
        for(auto& sock: m_socks){
            sock->cancelAll();
            sock->close();
        }
        m_socks.clear();
    });
}

std::string TcpServer::toString(const std::string& prefix){
    std::stringstream ss;
    ss << prefix << "[type=" << m_type
       << " name=" << m_name
       << " worker=" << (m_worker ? m_worker->getName(): "")
       << " accept=" << (m_acceptWorker ? m_acceptWorker->getName() : "")
       << " ioWorker=" << (m_ioWorker ? m_ioWorker->getName() : "")
       << " recvTimeout=" << m_recvTimeout << "]\n";
    std::string pfx = prefix.empty() ? "    " : prefix;
    for(auto& i : m_socks){
        ss << pfx << i << std::endl;
    }
    return ss.str();
}

bool TcpServer::loadCertificates(const std::string& cert_file, const std::string& key_file){
    for(auto& i : m_socks){
        SSLSocket::ptr ssl_socket = std::dynamic_pointer_cast<SSLSocket>(i);
        if(ssl_socket){
            if(!ssl_socket->loadCertificates(cert_file,key_file)){
                return false;
            }
            
        }
    }
    return true;
}



}